package dao

import (
	"context"
	"errors"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

type ArticleDAO interface {
	Insert(ctx context.Context, art Article) (int64, error)
	UpdateById(ctx context.Context, art Article) error
	Sync(ctx context.Context, art Article) (int64, error)
}

type ArticleGORMDAO struct {
	db *gorm.DB
}

func NewArticleGORMDAO(db *gorm.DB) ArticleDAO {
	return &ArticleGORMDAO{db: db}
}

func (a *ArticleGORMDAO) Insert(ctx context.Context, art Article) (int64, error) {
	now := time.Now().UnixMilli()
	art.Ctime = now
	art.Utime = now
	err := a.db.WithContext(ctx).Create(&art).Error
	return art.Id, err
}

func (a *ArticleGORMDAO) UpdateById(ctx context.Context, art Article) error {
	res := a.db.WithContext(ctx).Model(&Article{}).
		Where("id=? AND author_id=?", art.Id, art.AuthorId).Updates(map[string]any{
		"title":   art.Title,
		"content": art.Content,
		"utime":   time.Now().UnixMilli(),
	})
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		return errors.New("帖子更新失败，ID错误或者作者ID错误")
	}
	return nil
}

func (a *ArticleGORMDAO) Sync(ctx context.Context, art Article) (int64, error) {
	var id = art.Id
	err := a.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var (
			err error
		)
		dao := NewArticleGORMDAO(a.db)
		if id > 0 {
			err = dao.UpdateById(ctx, art)
		} else {
			id, err = dao.Insert(ctx, art)
		}
		if err != nil {
			return err
		}
		// 操作线上库
		art.Id = id
		now := time.Now().UnixMilli()
		publishedArt := PublishedArticle(art)
		publishedArt.Ctime = now
		publishedArt.Utime = now
		err = tx.Clauses(clause.OnConflict{
			DoUpdates: clause.Assignments(map[string]interface{}{
				"title":   publishedArt.Title,
				"content": publishedArt.Content,
				"utime":   now,
			}),
			Columns: []clause.Column{{Name: "id"}},
		}).Create(&publishedArt).Error
		return err
	})
	return id, err
}
func (a *ArticleGORMDAO) SyncV1(ctx context.Context, art Article) (int64, error) {
	tx := a.db.WithContext(ctx).Begin()
	if tx.Error != nil {
		return 0, tx.Error
	}
	defer tx.Rollback()
	var (
		id  = art.Id
		err error
	)
	dao := NewArticleGORMDAO(a.db)
	if id > 0 {
		err = dao.UpdateById(ctx, art)
	} else {
		id, err = dao.Insert(ctx, art)
	}
	if err != nil {
		return 0, err
	}
	art.Id = id
	now := time.Now().UnixMilli()
	publishedArt := PublishedArticle(art)
	publishedArt.Ctime = now
	publishedArt.Utime = now
	// 重点 upsert 写法
	// Clauses：gorm提供给我们，允许我们自定义一些sql语句
	// OnConflict：设置冲突时的操作，这里为更新某些字段。
	err = tx.Clauses(clause.OnConflict{
		DoUpdates: clause.Assignments(map[string]interface{}{
			"title":   publishedArt.Title,
			"content": publishedArt.Content,
			"utime":   now,
		}),
		// 这个对MySQL不起效果，但是可以兼容别的方言
		Columns: []clause.Column{{Name: "id"}},
	}).Create(&publishedArt).Error
	// 这里生成的INSERT语句大概是:
	// INSERT INTO table_name (column1, column2, ...)
	// VALUES (value1, value2, ...)
	// ON DUPLICATE KEY UPDATE column1 = value1, column2 = value2, ...;
	// 如果是其他方言，例如sqlite：INSERT xxx ON CONFLICT DO NOTHING/UPDATES
	if err != nil {
		return 0, err
	}
	// 提交事务
	tx.Commit()
	return id, nil
}

type Article struct {
	Id int64 `gorm:"primaryKey,autoIncrement"`
	// 标题的长度，一般不超过4096
	Title    string `gorm:"type=varchar(4096)"`
	AuthorId int64  `gorm:"index"`
	// BLOB 二进制形式的长文本数据
	Content string `gorm:"type=BLOB"`
	Ctime   int64  // 创建时间
	Utime   int64  // 更新时间
}

// PublishedArticle Repository层-事务实现，后可能会多字段，少字段，到时候再单独定义
type PublishedArticle Article
